iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 12
0
Modern Web

謙虛,踏實的Web Assembly練習系列 第 12

[練習 11] 嘗試應用看看...影像轉灰階

  • 分享至 

  • xImage
  •  

為什麼選灰階

其實是因為計算簡單XD

如果知道RGB,可以透過這樣的公式快速轉成灰階值:

Gray = (R*38 + G*75 + B*15) >> 7

(來源:彩色轉灰階原理(RGB To Grey)

這樣的計算,在WebAssembly要手寫應該不難才對XD(實際上在小地方就是會出錯)

取得RGB

Canvas 2D Context提供了一個getImageData()方法,可以從Canvas取得某範圍影像的RGBA資料。回傳的ImageData,其data屬性是一個陣列,內容分別是每個像素的R, G, B, A(Alpha),然後是下一個像素R, G, B, A等,依此類推。所以可以透過WebAssembly.Memory物件把這些資料傳入WebAssembly程式。

RGB轉灰階

如果是用Javascript處理,其實很簡單就是了:

	function jsgrayscale() {
		let in_image_data = canvas.getImageData(0, 0, 640, 480);
		for(let i=0; i<in_image_data.data.length; i+=4) {
			let r = in_image_data.data[i];
			let g = in_image_data.data[i+1];
			let b = in_image_data.data[i+2];
			let gr = (r*38 + g*75 + b*15) >> 7;
			in_image_data.data[i] = gr;
			in_image_data.data[i+1] = gr;
			in_image_data.data[i+2] = gr;
		}
		canvas.putImageData(in_image_data, 0, 0);
	}

用WebAssembly處理的話...為了怕寫回的時候出錯,改成input跟output資料放在不同位置的作法。傳給WebAssembly中的函數一個參數,也就是ImageData.data的長度,就會接著傳入的資料後面把東西寫入。

(module
	(memory (import "js" "buf") 1)
	(func (export "grey") (param $base i32)
		(local $in_ptr i32)
		(local $out_ptr i32)
		(local $grey i32)
		(local $r i32)
		(local $g i32)
		(local $b i32)
		i32.const 0
		set_local $in_ptr
		get_local $base
		set_local $out_ptr

		(block $break
			(loop $while
				;; read R from memory and calculate
				get_local $in_ptr
				i32.load8_u
				i32.const 38
				i32.mul
				set_local $r
				i32.const 1
				get_local $in_ptr
				i32.add
				tee_local $in_ptr

				;; read G from memory and calculate
				i32.load8_u
				i32.const 75
				i32.mul
				set_local $g
				i32.const 1
				get_local $in_ptr
				i32.add
				tee_local $in_ptr

				;; read B from memory and calculate
				i32.load8_u
				i32.const 15
				i32.mul
				set_local $b
				i32.const 1
				get_local $in_ptr
				i32.add
				set_local $in_ptr

				;; calculate grey
				get_local $r
				get_local $g
				i32.add
				get_local $b
				i32.add
				i32.const 7
				i32.shr_u
				set_local $grey

				;; write R to memory
				get_local $out_ptr
				get_local $grey
				i32.store8
				i32.const 1
				get_local $out_ptr
				i32.add
				tee_local $out_ptr

				;; write G to memory
				get_local $grey
				i32.store8
				i32.const 1
				get_local $out_ptr
				i32.add
				tee_local $out_ptr

				;; write B to memory
				get_local $out_ptr
				get_local $grey
				i32.store8
				i32.const 1
				get_local $out_ptr
				i32.add
				tee_local $out_ptr

				;; copy opacity from and to memory
				get_local $in_ptr
				i32.load8_u
				i32.store8
				i32.const 1
				get_local $out_ptr
				i32.add
				set_local $out_ptr
				i32.const 1
				get_local $in_ptr
				i32.add
				tee_local $in_ptr

				;; if $in_ptr greater or equal to $base then break
				get_local $base
				i32.ge_u
				br_if $break
				;; next iteration
				br $while
			)
		)
	)
)

html端很簡單地把影像寫入Canvas,然後按個Grayscale按鈕,就可以把影像轉成灰階。

<html>
<body>
	<canvas id="canvas" width="640" height="480"></canvas><br>
	<button id="grayscale">Grayscale</button><br>
	<div id="panel" style="display:none"></div>
	<script src="../wasm_util.js"></script>
	<script>
	let canvas = document.getElementById('canvas').getContext('2d');
	let img = new Image();
	let size = 640 * 480 * 4;
	let page_required = Math.floor(size * 2 / (64 * 1024)) + 1;
	let instance;
	let buf = new WebAssembly.Memory({initial:page_required});
	let importObjects = {
		js: {
			buf: buf
		}
	};
	img.src = 'IMG_1719_s.jpg';
	img.onload = function() {
		canvas.drawImage(img, 0, 0);
		instance = new Wasm('test014.wasm').getInstance(importObjects);
		console.log('image loaded');
	}
	document.getElementById('panel').appendChild(img);
	document.getElementById('grayscale').onclick = wasmgrayscale;
	function jsgrayscale() {
		let in_image_data = canvas.getImageData(0, 0, 640, 480);
		for(let i=0; i<in_image_data.data.length; i+=4) {
			let r = in_image_data.data[i];
			let g = in_image_data.data[i+1];
			let b = in_image_data.data[i+2];
			let gr = (r*38 + g*75 + b*15) >> 7;
			in_image_data.data[i] = gr;
			in_image_data.data[i+1] = gr;
			in_image_data.data[i+2] = gr;
		}
		canvas.putImageData(in_image_data, 0, 0);
	}
	function wasmgrayscale() {
		let in_image_data = canvas.getImageData(0, 0, 640, 480);
		let view = new Uint8ClampedArray(buf.buffer);
		for(let i=0; i<in_image_data.data.length; i++) {
			view[i] = in_image_data.data[i];
		}
		instance
		.then(instance => {
			instance.exports.grey(in_image_data.data.length);
			let view = new Uint8ClampedArray(buf.buffer);
			for(let i=size; i< (size*2); i++) {
				in_image_data.data[i-size] = view[i];
			}
			canvas.putImageData(in_image_data, 0, 0);
			console.log('grayscale done');
		});
	}
	</script>
</body>
</html>

(為了對照,裡面還有一個Javascript版的轉灰階函數jsgrayscale,這裡用的是WebAssembly裡的版本:wasmgrayscale)

先載入網頁,這時顯示的是彩色影像:

Imgur

按下Grayscale按鈕後,就轉成灰階:

Imgur

完工...

不過呢,其實程式寫了很多次XD...因為實際上在計算$in_ptr跟$out_ptr的時候有錯誤,$out_ptr多加了一次1,然後就出現Out of bound的錯誤,這是指定的記憶體位置超過超過記憶體範圍。還好重新檢查過後,發現錯誤,不然光靠這個資訊,也很難判斷問題出在哪裡。錯誤資訊雖然有標注出錯的行數,但這是反組譯出來的原始碼行數,並不是自己寫的程式的行數。


據說Firefox的開發工具,從54+版之後已經有除錯WebAssembly的功能,明天來試試看...(過年少寫一天程式)


上一篇
[練習 10] 對於區塊理解的錯誤以及br_table的寫法
下一篇
[練習 12] 怎麼除錯
系列文
謙虛,踏實的Web Assembly練習20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言